/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
#include <fstream>
#include <mc/filesystem.h>
#include <mc/log.h>
#include <sstream>

#include "device/driver.h"
#include "devmon.h"
#include "discovery/app_schema.h"
#include "topology/topology_discovery.h"
#include "utils.h"

namespace devmon {

constexpr const char* DRIVERS_LOAD_INFO_FILE      = "/drivers_load_info.csv";
constexpr const char* CHIPS_ACCESS_STATISTIC_FILE = "/chips_access_statistic.csv";
constexpr const char* SNAPSHOT_FILE               = "/snapshot.csv";

bool devmon_service::init(mc::dict args) {
    devmon_config config;
    mc::from_variant(args, config);

    if (!mc::engine::service::init(args)) {
        return false;
    }

    // 启动先解析schema文件
    app_schema::get_instance().init(config.app_paths);

    device_driver_factory::get_instance().set_driver_path(config.driver_path);

    ilog("Service: ${name} initialization completed", ("name", name()));
    return true;
}

bool devmon_service::start() {
    if (!mc::engine::service::start()) {
        return false;
    }

    m_root_object      = root_object::create_root_object(this);
    m_root_topo_object = root_topo_object::create_root_topo_object(this);
    object_group_root::create_object(this);
    ilog("Starting device manager service: ${name}", ("name", name()));

    device_topo::get_instance().init(this);
    topology_discovery::get_instance(this).normal_discovery();

    return true;
}

bool devmon_service::stop() {
    try {
        topology_discovery::get_instance().cleanup();
    } catch (const std::exception& e) {
        wlog("Exception occurred while stopping topology_discovery: ${error}", ("error", e.what()));
    }

    if (!mc::engine::service::stop()) {
        return false;
    }
    ilog("Stopping device manager service: ${name}", ("name", name()));
    return true;
}

void devmon_service::cleanup() {
    ilog("Cleaning up device manager service resources: ${name}", ("name", name()));
}

void devmon_service::dump_drivers_load_info(std::string filepath) {
    std::ostringstream oss;
    oss << "DriverName,Load_status,LoadTime(ms),DeviceCount,ErrorMessage\n";

    for (const auto& info : devmon::device_driver::s_load_info_table) {
        oss << info.driver_name << "," << (info.success ? "success" : "fail") << "," << std::fixed
            << std::setprecision(3) << info.load_time_ms << "," << info.device_count << ","
            << info.error_msg << "\n";
    }

    std::string full_path = filepath + DRIVERS_LOAD_INFO_FILE;
    try {
        mc::filesystem::write_file(full_path, oss.str());
        ilog("Drivers load info dumped to ${file}", ("file", full_path));
    } catch (const std::exception& e) {
        elog("Failed to write ${file}: ${err}", ("file", full_path)("err", e.what()));
    } catch (...) {
        elog("Failed to write ${file}: unknown error", ("file", full_path));
    }
}

void devmon_service::dump_chips_access_info(std::string filepath) {
    auto& table   = get_object_table();
    auto  root_it = table.find_object(mc::engine::by_object_name::field == "DevTopology");
    if (!root_it) {
        elog("root topology object missing, dumping aborted");
        return;
    }

    std::ostringstream oss;
    oss << "Bus,Chip,Write_success_count,Write_failed_count,Read_sucess_count,"
        << "Read_failed_count,Statistic_Time(s),Chip_access_time(sec),Error_Recording\n";

    auto* root_obj = static_cast<mc::engine::abstract_object*>(root_it.get());

    object_group_data&     obj_group           = object_group_data::get_instance();
    device_driver_factory& factory             = device_driver_factory::get_instance();
    const std::string      chip_interface_name = "bmc.dev.Chip";

    auto dump_single_chip = [&](mc::engine::abstract_object* obj, std::ostringstream& out) {
        std::string class_name = std::string(obj->get_class_name());
        if (class_name.empty()) {
            return;
        }

        device_driver* drv = nullptr;
        try {
            drv = &factory.get_device_driver(class_name);
        } catch (...) {
            elog("No driver found for ${chip}", ("chip", class_name));
            return;
        }
        if (!drv) {
            return;
        }

        device_driver_t& driver = drv->get_device(const_cast<std::string&>(class_name));
        if (!driver.dump) {
            return;
        }

        std::string obj_name = std::string(obj->get_object_name());
        if (obj_name.size() >= 4 && obj_name.compare(obj_name.size() - 4, 4, "_dev") == 0) {
            obj_name.resize(obj_name.size() - 4);
        }

        mc::engine::abstract_object* device_obj =
            obj_group.get_device_object(std::string(obj->get_position()), obj_name);

        if (!device_obj) {
            elog("No driver object found for ${name}", ("name", obj_name));
            return;
        }

        const char* dump_cstr = driver.dump(static_cast<driver_handle_t>(device_obj));
        std::string dump_text = dump_cstr ? dump_cstr : "";
        if (!dump_text.empty()) {
            out << dump_text;
        } else {
            elog("Failed to dump ${obj_name}", ("obj_name", obj_name));
        }
    };

    auto process_chip_object = [&](mc::engine::abstract_object* obj, auto&& recurse) -> void {
        if (!obj) {
            return;
        }
        try {
            if (obj->has_interface(chip_interface_name)) {
                dump_single_chip(obj, oss);
            }

            const auto& children = obj->get_managed_objects();
            for (const auto& [_, child] : children) {
                recurse(child, recurse);
            }
        } catch (const std::exception& e) {
            elog("Exception while processing object: ${error}", ("error", e.what()));
        } catch (...) {
            elog("Unknown exception while processing object");
        }
    };

    process_chip_object(root_obj, process_chip_object);
    std::string full_path = filepath + CHIPS_ACCESS_STATISTIC_FILE;
    try {
        mc::filesystem::write_file(full_path, oss.str());
        ilog("Chips access info dumped to ${path}", ("path", full_path));
    } catch (const std::exception& e) {
        elog("Failed to write ${file}: ${err}", ("file", full_path)("err", e.what()));
    } catch (...) {
        elog("Failed to write ${file}: unknown error", ("file", full_path));
    }
}

void devmon_service::dump_scanners_info(std::string filepath) {
    auto& table = get_object_table();
    auto& index = table.get<mc::engine::by_class_name>();

    std::string scanner_type = "Scanner";

    auto scanner_range = index.equal_range(scanner_type);

    std::string full_path = filepath + SNAPSHOT_FILE;

    device_driver_factory& factory   = device_driver_factory::get_instance();
    object_group_data&     obj_group = object_group_data::get_instance();

    device_driver* drv = nullptr;

    try {
        drv = &factory.get_device_driver(scanner_type);
    } catch (...) {
        elog("No driver found for Scanner");
        return;
    }

    device_driver_t& scanner_driver = drv->get_device(const_cast<std::string&>(scanner_type));
    if (scanner_driver.dump == nullptr) {
        elog("Scanner driver has no dump method");
        return;
    }

    std::ostringstream oss;
    oss << "Chip,Accessor/Scanner,Period,Aggregate_status,Status,Scan_status,Success_count,"
        << "Failure_count,Value,Emit_signal_count,Error\n";

    for (auto it = scanner_range.first; it != scanner_range.second; ++it) {
        const mc::engine::abstract_object* scanner_obj = &(*it);
        if (!scanner_obj) {
            continue;
        }

        std::string obj_name = std::string(scanner_obj->get_object_name());
        // 去除dev后缀
        if (obj_name.size() >= 4 && obj_name.compare(obj_name.size() - 4, 4, "_dev") == 0) {
            obj_name.resize(obj_name.size() - 4);
        }

        mc::engine::abstract_object* device_obj =
            obj_group.get_device_object(std::string(scanner_obj->get_position()), obj_name);
        if (!device_obj) {
            elog("No driver object found for ${name}", ("name", obj_name));
            continue;
        }

        driver_handle_t handle = static_cast<driver_handle_t>(device_obj);

        const char* dump_cstr = scanner_driver.dump(handle);
        std::string dump_text = dump_cstr ? dump_cstr : "";
        if (!dump_text.empty()) {
            oss << dump_text;
        } else {
            elog("Failed to dump scanner ${name}", ("name", obj_name));
        }
    }

    try {
        mc::filesystem::write_file(full_path, oss.str());
        ilog("Scanners info dumped to ${path}", ("path", full_path));
    } catch (const std::exception& e) {
        elog("Failed to write ${file}: ${err}", ("file", full_path)("err", e.what()));
    } catch (...) {
        elog("Failed to write ${file}: unknown error", ("file", full_path));
    }
}

void devmon_service::dump_accessors_info(std::string filepath) {
    auto& table = get_object_table();
    auto& index = table.get<mc::engine::by_class_name>();

    std::string accessor_type = "Accessor";

    auto accessor_range = index.equal_range(accessor_type);

    std::string full_path = filepath + SNAPSHOT_FILE;

    device_driver_factory& factory   = device_driver_factory::get_instance();
    object_group_data&     obj_group = object_group_data::get_instance();

    device_driver* drv = nullptr;

    try {
        drv = &factory.get_device_driver(accessor_type);
    } catch (...) {
        elog("No driver found for Accessor");
        return;
    }

    device_driver_t& accessor_driver = drv->get_device(const_cast<std::string&>(accessor_type));
    if (accessor_driver.dump == nullptr) {
        elog("Accessor driver has no dump method");
        return;
    }

    std::ostringstream oss;

    for (auto it = accessor_range.first; it != accessor_range.second; ++it) {
        const mc::engine::abstract_object* accessor_obj = &(*it);
        if (!accessor_obj) {
            continue;
        }

        std::string obj_name = std::string(accessor_obj->get_object_name());
        // 去除dev后缀
        if (obj_name.size() >= 4 && obj_name.compare(obj_name.size() - 4, 4, "_dev") == 0) {
            obj_name.resize(obj_name.size() - 4);
        }

        mc::engine::abstract_object* device_obj =
            obj_group.get_device_object(std::string(accessor_obj->get_position()), obj_name);
        if (!device_obj) {
            elog("No driver object found for ${name}", ("name", obj_name));
            continue;
        }

        driver_handle_t handle = static_cast<driver_handle_t>(device_obj);

        const char* dump_cstr = accessor_driver.dump(handle);
        std::string dump_text = dump_cstr ? dump_cstr : "";
        if (!dump_text.empty()) {
            oss << dump_text;
        } else {
            elog("Failed to dump accessor ${name}", ("name", obj_name));
        }
    }

    try {
        if (mc::filesystem::exists(full_path)) {
            mc::filesystem::append_file(full_path, oss.str());
        } else {
            std::ostringstream header;
            header << "Chip,Accessor/Scanner,Period,Aggregate_status,Status,Scan_status,Success_count,"
                   << "Failure_count,Value,Emit_signal_count,Error\n";
            mc::filesystem::write_file(full_path, header.str() + oss.str());
        }
        ilog("Accessors info dumped to ${path}", ("path", full_path));
    } catch (const std::exception& e) {
        elog("Failed to write ${file}: ${err}", ("file", full_path)("err", e.what()));
    } catch (...) {
        elog("Failed to write ${file}: unknown error", ("file", full_path));
    }
}

void devmon_service::on_dump(std::map<std::string, std::string> context, std::string file_path) {
    device_topo::get_instance().dump_topology(file_path);
    dump_scanners_info(file_path);
    dump_accessors_info(file_path);
    dump_drivers_load_info(file_path);
    if (!devmon::utils::is_release_build()) {
        dump_chips_access_info(file_path);
    }
}
} // namespace devmon

MC_REFLECT(devmon::devmon_config, ((driver_path, "driver_path"))((app_paths, "app_paths")))
